﻿//-----------------------------------------------------------------------
// <copyright file="ChatChannel.cs" company="FastNET Services">
//     Copyright (c) 2008-2009. All Rights Reserved.
// </copyright>
// <license>
//     GPLv2
// </license>
// <warranty>
//     THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
//     BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
//     NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
//     DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//     OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
// </warranty>
//-----------------------------------------------------------------------
#region Usings

using System;
using System.Collections.Generic;
using GateShift.Services.ChatSystem.Backup.Exceptions;
using GateShift.Services.ChatSystem.Interfaces;

#endregion

namespace GateShift.Services.ChatSystem.Backup
{
    /// <summary>
    /// Class for managing a single chat channel
    /// </summary>
    internal class ChatChannel : IChatChannel
    {
        #region Variables

        private readonly List<Guid> _members;
        private readonly List<Guid> _moderators;
        private readonly List<Guid> _bannedUsers;
        private string _password;
        private Guid _owner;

        #endregion

        #region CTor/DTor

        /// <summary>
        /// Initializes a new instance of the ChatChannel class
        /// </summary>
        /// <param name="owner">Initial Owner of the channel</param>
        /// <param name="name">Initial name for the channel</param>
        public ChatChannel(Guid owner, string name)
        {
            // Make new Lists
            _members = new List<Guid>();
            _moderators = new List<Guid>();
            _bannedUsers = new List<Guid>();

            // Add Owner
            _owner = owner;
            _members.Add(owner);
            _moderators.Add(owner);

            Name = name;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the name for the channel
        /// </summary>
        public string Name { get; set; }

        #endregion

        #region Admin Methods

        /// <summary>
        /// Sets a new channel owner
        /// </summary>
        /// <param name="memberID">Member to promote to the owner</param>
        /// <param name="ownerID">Current owner requesting the change. Use NULL for system command.</param>
        public void SetChannelOwner(Guid memberID, Guid? ownerID)
        {
            if (ownerID.HasValue && _owner != ownerID)
            {
                throw new SecurityChatChannelException("Specified owner is not the current owner");
            }

            if (!_members.Contains(memberID))
            {
                throw new InvalidOperationException("New owner is not a member of the channel");
            }

            // Make the new member a moderator too
            if (!_moderators.Contains(memberID))
            {
                _moderators.Add(memberID);
            }

            _owner = memberID;
        }

        /// <summary>
        /// Sets a password for the channel. Setting password to NULL removes the password.
        /// </summary>
        /// <param name="ownerID">Owner of the channel requesting a new password. Use NULL for system command.</param>
        /// <param name="password">Password to use when joining the channel</param>
        public void SetChannelPassword(Guid? ownerID, string password)
        {
            if (ownerID.HasValue && _owner != ownerID)
            {
                throw new SecurityChatChannelException("Specified owner is not the current owner");
            }

            _password = password;
        }

        #endregion

        #region Moderator Methods

        /// <summary>
        /// Adds the specified moderator given another moderator account
        /// </summary>
        /// <param name="memberID">User to add to the Moderator list. If the user is in the list, the request is ignored</param>
        /// <param name="moderatorID">Moderator making the request. NULL should only be used if system is making request.</param>
        public void AddModerator(Guid memberID, Guid? moderatorID)
        {
            // Validate Request
            if (moderatorID.HasValue && !_moderators.Contains(moderatorID.Value))
            {
                throw new InvalidOperationException("Moderator is not a member of the channel");
            }

            if (_moderators.Contains(memberID))
            {
                return;
            }

            // Add to the moderators
            _moderators.Add(memberID);
        }

        /// <summary>
        /// Demotes the specified moderator given another moderator account
        /// </summary>
        /// <param name="memberID">User to remove from the Moderator list. If the user is not in the list, the request is ignored</param>
        /// <param name="moderatorID">Moderator making the request. NULL should only be used if system is making request.</param>
        public void RemoveModerator(Guid memberID, Guid? moderatorID)
        {
            // Validate Request
            if (_owner == memberID)
            {
                throw new InvalidOperationException("Specified user is the owner");
            }

            if (moderatorID.HasValue && moderatorID.Value == memberID)
            {
                throw new InvalidOperationException("Specified user is the requesting moderator");
            }

            if (moderatorID.HasValue && !_moderators.Contains(moderatorID.Value))
            {
                throw new InvalidOperationException("Moderator is not a member of the channel");
            }

            // Remove from the moderators
            _moderators.Remove(memberID);
        }

        #endregion

        #region Ban/Un-Ban Methods

        /// <summary>
        /// Bans the specified user given a moderator account
        /// </summary>
        /// <param name="userID">User to remove from the ban list. If the user is not in the list, the request is ignored</param>
        /// <param name="moderatorID">Moderator making the request. NULL should only be used if system is making request.</param>
        /// <exception cref="InvalidOperationException">Thrown if the user is the owner or the requesting moderator</exception>
        /// <exception cref="SecurityChatChannelException">Thrown if the moderator could not be found</exception>
        public void BanMember(Guid userID, Guid? moderatorID)
        {
            // Validate Request
            if (_owner == userID)
            {
                throw new InvalidOperationException("Specified user is the owner");
            }

            if (moderatorID.HasValue && moderatorID.Value == userID)
            {
                throw new InvalidOperationException("Specified user is the requesting moderator");
            }

            if (moderatorID.HasValue && !_moderators.Contains(moderatorID.Value))
            {
                throw new SecurityChatChannelException("Moderator is not a member of the channel");
            }

            if (_bannedUsers.Contains(userID))
            {
                return;
            }

            // Remove the user from any moderator or member role
            _moderators.Remove(userID);
            _members.Remove(userID);

            // Add to the do not allow list
            _bannedUsers.Add(userID);
        }

        /// <summary>
        /// Un-Bans the specified user given a moderator account, but does not add the user to the member list
        /// </summary>
        /// <param name="userID">User to remove from the ban list. If the user is not in the list, the request is ignored</param>
        /// <param name="moderatorID">Moderator making the request. NULL should only be used if system is making request.</param>
        public void UnBanMember(Guid userID, Guid? moderatorID)
        {
            if (moderatorID.HasValue && !_moderators.Contains(moderatorID.Value))
            {
                throw new SecurityChatChannelException("Moderator is not a member of the channel");
            }

            // Remove from the banlist
            _bannedUsers.Remove(userID);
        }

        #endregion

        #region General Methods

        /// <summary>
        /// Forces the specified user into the chat channel
        /// </summary>
        /// <param name="requestorID">User to add to the chat channel</param>
        public void SystemJoin(Guid requestorID)
        {
            _bannedUsers.Remove(requestorID); // We must unban users to prevent conflicts later
            _members.Add(requestorID);
        }

        /// <summary>
        /// Joins the specified user to the chat channel as long as they provide the right password
        /// </summary>
        /// <param name="requestorID">User joining the channel</param>
        /// <param name="password">Password used when joining the channel</param>
        /// <exception cref="SecurityChatChannelException">Thrown if the user does not have permission to join the chat channel</exception>
        /// <exception cref="PasswordChatChannelException">Thrown if the password does not match</exception>
        public void JoinChannel(Guid requestorID, string password)
        {
            if (!CanJoin(requestorID))
            {
                throw new SecurityChatChannelException("User cannot join this channel");
            }

            if (password != _password)
            {
                throw new PasswordChatChannelException("Password Mismatch");
            }

            _members.Add(requestorID);
        }

        /// <summary>
        /// Makes the specified user leave the chat channel as long as they are not the owner
        /// </summary>
        /// <param name="requestorID">User leaving the channel</param>
        /// <exception cref="InvalidOperationException">Thrown if requestorID is the owner</exception>
        public void LeaveChannel(Guid requestorID)
        {
            if (IsOwner(requestorID))
            {
                throw new InvalidOperationException("Owner cannot leave channel. Channel must be deleted instead.");
            }

            _moderators.Remove(requestorID);
            _members.Remove(requestorID);
        }

        /// <summary>
        /// Removes all members except for the owner from the chat channel
        /// </summary>
        public void ClearAllMembers()
        {
            _members.Clear();
            _moderators.Clear();
            _bannedUsers.Clear();

            // Re-add the owner to the members
            _members.Add(_owner);
            _moderators.Add(_owner);
        }

        #endregion

        #region Permission Checks

        /// <summary>
        /// Returns whether or not the specified user is a member of the channel
        /// </summary>
        /// <param name="requestorID">User to check for member role</param>
        /// <returns>Boolean whether or not the specified user is a member</returns>
        public bool IsMember(Guid requestorID)
        {
            return _members.Contains(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user is a moderator of the channel
        /// </summary>
        /// <param name="requestorID">User to check for moderator role</param>
        /// <returns>Boolean whether or not the specified user is a moderator</returns>
        public bool IsModerator(Guid requestorID)
        {
            return _moderators.Contains(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user is banned from the channel
        /// </summary>
        /// <param name="requestorID">User to check for banment</param>
        /// <returns>Boolean whether or not the specified user is currently banned</returns>
        public bool IsBanned(Guid requestorID)
        {
            return !_bannedUsers.Contains(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user is the owner of the channel
        /// </summary>
        /// <param name="requestorID">User to check for ownership</param>
        /// <returns>Boolean whether or not the specified user is the owner</returns>
        public bool IsOwner(Guid requestorID)
        {
            return requestorID == _owner;
        }

        /// <summary>
        /// Returns whether or not the specified user can set a password for the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanSetPassword(Guid requestorID)
        {
            return IsOwner(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can set an owner for the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanSetOwner(Guid requestorID)
        {
            return IsOwner(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can delete the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanDelete(Guid requestorID)
        {
            return IsOwner(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can modify a ban on the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanModifyBan(Guid requestorID)
        {
            return IsModerator(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can modify a moderator permission for the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanModifyModerator(Guid requestorID)
        {
            return IsModerator(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can invite a user to the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanInviteMember(Guid requestorID)
        {
            return IsMember(requestorID);
        }

        /// <summary>
        /// Returns whether or not the specified user can join the channel
        /// </summary>
        /// <param name="requestorID">User requesting permission</param>
        /// <returns>Boolean whether or not the specified user can perform the action</returns>
        public bool CanJoin(Guid requestorID)
        {
            return IsBanned(requestorID);
        }

        #endregion
    }
}